package org.example.time.util;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Comparator;
import java.util.Date;

/**
 * 时间日期工具
 * <p>
 * 返回 LocalDateTime 格式的日、周、月、季度、年起止时间
 *
 * @author Yunkai Luo
 **/
public class LocalDateTimeUtil {
    private static final DateTimeFormatter DEFAULT_FORMATTER = new DateTimeFormatterBuilder()
            .appendPattern("yyyy-MM-dd[['T'HH][:mm][:ss]]")
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
            .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
            .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)
            .toFormatter();
    Comparator<LocalDateTime> LOCAL_DATE_TIME_COMPARATOR = (t1, t2) -> {
        if (t1 == null) {
            return -1;
        } else if (t2 == null) {
            return 1;
        }

        if (t1.isBefore(t2)) {
            return -1;
        } else if (t2.isBefore(t1)) {
            return 1;
        }

        return 0;
    };

    /**
     * @Description:当天的开始时间
     * @Param: [today, isFirst: true 表示开始时间，false表示结束时间]
     */
    public static LocalDateTime getStartOrEndDayOfDay(LocalDate today, Boolean isFirst) {
        LocalDate resDate = LocalDate.now();
        if (today == null) {
            today = resDate;
        }
        if (isFirst) {
            return LocalDateTime.of(today, LocalTime.MIN);
        } else {
            return LocalDateTime.of(today, LocalTime.MAX);
        }
    }

    public static LocalDateTime getStartOrEndDayOf(ClassificationOfTimeEnum classificationOfTimeEnum, LocalDateTime today, Boolean isFirst) {
        switch (classificationOfTimeEnum) {
            case DAY:
                return getStartOrEndDayOfDay(today.toLocalDate(), isFirst);
            case MONTH:
                return getStartOrEndDayOfMonth(today.toLocalDate(), isFirst);
            case YEAR:
                return getStartOrEndDayOfYear(today.toLocalDate(), isFirst);
            case WEEK:
                return getStartOrEndDayOfWeek(today.toLocalDate(), isFirst);
            case QUARTER:
                return getStartOrEndDayOfQuarter(today.toLocalDate(), isFirst);
            case HOUR:
                return getStartOrEndDayOfHour(today, isFirst);
            case MINUTE:
                return getStartOrEndDayOfMinute(today, isFirst);
            case SECOND:
                return getStartOrEndDayOfSecond(today, isFirst);
            default:
                return null;
        }
    }

    public static LocalDateTime getStartOrEndDayOfHour(LocalDateTime today, Boolean isFirst) {
        if (today == null) {
            today = LocalDateTime.now();
        }
        if (isFirst) {
            return LocalDateTime.of(today.toLocalDate(), LocalTime.of(today.getHour(), 0, 0, 0));
        } else {
            return LocalDateTime.of(today.toLocalDate(), LocalTime.of(today.getHour(), 59, 59, 999_999_999));
        }
    }

    public static LocalDateTime getStartOrEndDayOfMinute(LocalDateTime today, Boolean isFirst) {
        if (today == null) {
            today = LocalDateTime.now();
        }
        if (isFirst) {
            return LocalDateTime.of(today.toLocalDate(), LocalTime.of(today.getHour(), today.getMinute(), 0, 0));
        } else {
            return LocalDateTime.of(today.toLocalDate(), LocalTime.of(today.getHour(), today.getMinute(), 59, 999_999_999));
        }
    }

    public static LocalDateTime getStartOrEndDayOfSecond(LocalDateTime today, Boolean isFirst) {
        if (today == null) {
            today = LocalDateTime.now();
        }
        if (isFirst) {
            return LocalDateTime.of(today.toLocalDate(), LocalTime.of(today.getHour(), today.getMinute(), today.getSecond(), 0));
        } else {
            return LocalDateTime.of(today.toLocalDate(), LocalTime.of(today.getHour(), today.getMinute(), today.getSecond(), 999_999_999));
        }
    }

    /**
     * @Description:本周的开始时间
     * @Param: [today, isFirst: true 表示开始时间，false表示结束时间]
     */
    public static LocalDateTime getStartOrEndDayOfWeek(LocalDate today, Boolean isFirst) {
        LocalTime time = LocalTime.MIN;
        LocalDate resDate = LocalDate.now();
        if (today == null) {
            today = resDate;
        }
        DayOfWeek week = today.getDayOfWeek();
        int value = week.getValue();
        if (isFirst) {
            resDate = today.minusDays(value - 1);
        } else {
            resDate = today.plusDays(7 - value);
            time = LocalTime.MAX;
        }
        return LocalDateTime.of(resDate, time);
    }

    /**
     * @return
     * @Description:本月的开始时间
     * @Param: [today, isFirst: true 表示开始时间，false表示结束时间]
     */
    public static LocalDateTime getStartOrEndDayOfMonth(LocalDate today, Boolean isFirst) {
        LocalTime time = LocalTime.MIN;
        LocalDate resDate = LocalDate.now();
        if (today == null) {
            today = resDate;
        }
        Month month = today.getMonth();
        int length = month.length(today.isLeapYear());
        if (isFirst) {
            resDate = LocalDate.of(today.getYear(), month, 1);
        } else {
            resDate = LocalDate.of(today.getYear(), month, length);
            time = LocalTime.MAX;
        }
        return LocalDateTime.of(resDate, time);
    }

    /**
     * @Description:本季度的开始时间
     * @Param: [today, isFirst: true 表示开始时间，false表示结束时间]
     */
    public static LocalDateTime getStartOrEndDayOfQuarter(LocalDate today, Boolean isFirst) {
        LocalTime time = LocalTime.MIN;
        LocalDate resDate = LocalDate.now();
        if (today == null) {
            today = resDate;
        }
        Month month = today.getMonth();
        Month firstMonthOfQuarter = month.firstMonthOfQuarter();
        Month endMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
        if (isFirst) {
            resDate = LocalDate.of(today.getYear(), firstMonthOfQuarter, 1);
        } else {
            resDate = LocalDate.of(today.getYear(), endMonthOfQuarter, endMonthOfQuarter.length(today.isLeapYear()));
            time = LocalTime.MAX;
        }
        return LocalDateTime.of(resDate, time);
    }

    /**
     * @Description:本年度的开始时间
     * @Param: [today, isFirst: true 表示开始时间，false表示结束时间]
     */
    public static LocalDateTime getStartOrEndDayOfYear(LocalDate today, Boolean isFirst) {
        LocalTime time = LocalTime.MIN;
        LocalDate resDate = LocalDate.now();
        if (today == null) {
            today = resDate;
        }
        if (isFirst) {
            resDate = LocalDate.of(today.getYear(), Month.JANUARY, 1);
        } else {
            resDate = LocalDate.of(today.getYear(), Month.DECEMBER, Month.DECEMBER.length(today.isLeapYear()));
            time = LocalTime.MAX;
        }
        return LocalDateTime.of(resDate, time);
    }

    /**
     * 从time按指定格式化器格式化
     *
     * @param time 时间
     * @return 时间字符串
     */
    public static String format(TemporalAccessor time) {
        return format(time, DateTimeFormatter.ISO_DATE);
    }

    /**
     * 从time按指定格式化器格式化
     *
     * @param time      时间
     * @param formatter 格式化器
     * @return 时间字符串
     */
    public static String format(TemporalAccessor time, DateTimeFormatter formatter) {
        if (null == time) {
            return null;
        }

        if (null == formatter) {
            formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        }

        return formatter.format(time);
    }

    public static LocalDateTime deserialize(String date) {
        return LocalDateTime.parse(date, DEFAULT_FORMATTER);
    }

    /**
     * 替代 System.currentTimeMillis()
     *
     * @return 时间戳
     */
    public static long currentTimeMillis() {
        Instant instant = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
        return instant.toEpochMilli();
    }

    public static LocalDateTime toDate(Date day) {
        return day.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    public static long toInstant(LocalDateTime dateTime) {
        return dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    public static Date toDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

}
